#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <plugin.h>
#include "object.h"
#include "material.h"
#include "bvol.h"
#include "scene.h"
#include "xmlhelper.h"

struct object {
	char *name;
	char *type;
	void *data;

	char *matref;
	struct material *mat;	/* cached material pointer */

	mat4_t xform;		/* transformation matrix */
	mat4_t inv_xform;	/* inverse of the transformation matrix */

	void *so;

	struct scene *scn;

	/* free object data */
	void (*free_data)(void*);

	/* extract object data from the xml tree */
	void *(*read_xml)(struct xml_node*);

	/* build an xml tree from the object data */
	struct xml_tree *(*write_xml)(void*);

	/* get a bounding sphere */
	struct bsphere *(*get_bsph)(void*);

	/* print information about this object */
	void (*print_info)(void*, FILE*);

	/* check for ray-object intersections */
	int (*intersect)(void*, mat4_t, mat4_t, ray_t, struct isect_point*);
};

static int load_plugin(struct object *obj);


struct object *obj_create(void)
{
	struct object *obj;

	if(!(obj = malloc(sizeof *obj))) {
		return 0;
	}
	memset(obj, 0, sizeof *obj);

	m4_identity(obj->xform);
	m4_identity(obj->inv_xform);
	/* TODO continue init */

	return obj;
}

void obj_free(struct object *obj)
{
	if(!obj) return;

	free(obj->name);
	free(obj->type);
	free(obj->matref);

	if(obj->free_data && obj->data) {
		obj->free_data(obj->data);
	}

	if(obj->so) {
		dlclose(obj->so);
	}
	free(obj);
}

void obj_print_info(struct object *obj, FILE *fp)
{
	obj->print_info(obj->data, fp);
}

int obj_from_xml(struct object *obj, struct xml_node *xml)
{
	struct xml_node *c;
	struct xml_attr *attr;

	if((attr = xml_get_attr(xml, "name"))) {
		obj_set_name(obj, attr->str);
	} else {
		obj_set_name(obj, "anonymous");
	}

	if((attr = xml_get_attr(xml, "type"))) {
		obj_set_type(obj, attr->str);

		if(load_plugin(obj) == -1) {
			return -1;
		}

	} else {
		fprintf(stderr, "object: %s, missing type attribute\n", obj->name);
		return -1;
	}

	c = xml->chld;
	while(c) {
		if(strcmp(c->name, "matref") == 0) {
			if((attr = xml_get_attr(c, "name"))) {
				struct material *mat;
				if((mat = scn_get_material(obj->scn, attr->str))) {
					obj_set_material(obj, mat);
				} else {
					fprintf(stderr, "object: %s, material %s not found\n", obj->name, attr->str);
				}
			}
		} else if(strcmp(c->name, "xform") == 0) {
			xmlh_get_xform(obj->xform, c);
			m4_inverse(obj->inv_xform, obj->xform);
		}
		c = c->next;
	}

	if(!(obj->data = obj->read_xml(xml))) {
		return -1;
	}
	return 0;
}

struct xml_node *obj_to_xml(struct object *obj)
{
	return 0;	/* TODO */
}

int obj_intersect(struct object *obj, ray_t ray, struct isect_point *pt)
{
	return obj->intersect(obj->data, obj->xform, obj->inv_xform, ray, pt);
}

int obj_set_name(struct object *obj, const char *name)
{
	char *tmp;

	if(!(tmp = malloc(strlen(name) + 1))) {
		return -1;
	}
	strcpy(tmp, name);

	free(obj->name);
	obj->name = tmp;
	return 0;
}

const char *obj_get_name(struct object *obj)
{
	return obj->name;
}

int obj_set_type(struct object *obj, const char *type)
{
	char *tmp;

	if(!(tmp = malloc(strlen(type) + 1))) {
		return -1;
	}
	strcpy(tmp, type);

	free(obj->type);
	obj->type = tmp;
	return 0;
}

const char *obj_get_type(struct object *obj)
{
	return obj->type;
}

void obj_set_material(struct object *obj, struct material *mat)
{
	obj->mat = mat;
}

struct material *obj_get_material(struct object *obj)
{
	return obj->mat;
}

void obj_set_xform(struct object *obj, mat4_t xform)
{
	m4_copy(obj->xform, xform);
}

void obj_get_xform(struct object *obj, mat4_t xform)
{
	m4_copy(xform, obj->xform);
}

void obj_get_inv_xform(struct object *obj, mat4_t xform)
{
	m4_copy(xform, obj->inv_xform);
}

void obj_set_objdata(struct object *obj, void *data)
{
	obj->data = data;
}

void *obj_get_objdata(struct object *obj)
{
	return obj->data;
}

#define GET_FUNC(name)	\
	if(!(obj->name = dlsym(obj->so, #name))) { \
		fprintf(stderr, "object plugin \"%s\" doesn't provide a \"" #name "\" function\n", obj->type); \
		dlclose(obj->so); \
		return -1; \
	}

static int load_plugin(struct object *obj)
{
	void (*pinfo)(FILE*);

	if(!(obj->so = get_plugin(obj->type))) {
		fprintf(stderr, "missing object plugin for type: %s\n", obj->type);
		return -1;
	}

	fprintf(stderr, "Loading: ");
	if((pinfo = dlsym(obj->so, "plugin_info"))) {
		pinfo(stderr);
	} else {
		fprintf(stderr, "%s plugin (geometry)\n", obj->type);
	}

	GET_FUNC(free_data);
	GET_FUNC(read_xml);
	GET_FUNC(write_xml);
	GET_FUNC(get_bsph);
	GET_FUNC(print_info);
	GET_FUNC(intersect);
	return 0;
}

void _obj_impl_set_scene(struct object *obj, struct scene *scn)
{
	obj->scn = scn;
}
